package com.example.oauth2.component;

import com.example.common.utils.Statistics;
import com.example.oauth2.utils.AuthenticationUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;

import java.util.Map;

/**
 * @author <a href="nav1cat@outlook.com">nav1c</a>
 * @apiNote
 * @date 2024-01-25 11:09:14
 * @project study-cloud
 * @kit IntelliJ IDEA
 */
@Slf4j
public class PasswordAuthenticationProvider implements AuthenticationProvider {

    private static volatile PasswordAuthenticationProvider instance;

    private final AuthorizationGrantType authorizationGrantType;

    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;

    private final OAuth2AuthorizationService authorizationService;

    private final DaoAuthenticationProvider daoAuthenticationProvider;

    private PasswordAuthenticationProvider(OAuth2AuthorizationService authorizationService,
                                          OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
                                          UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.authorizationGrantType = AuthorizationGrantType.PASSWORD;
        this.tokenGenerator = tokenGenerator;
        this.authorizationService = authorizationService;
        this.daoAuthenticationProvider = new DaoAuthenticationProvider(passwordEncoder);
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
    }

    public static PasswordAuthenticationProvider getInstance(OAuth2AuthorizationService authorizationService,
                                                             OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
                                                             UserDetailsService userDetailsService,
                                                             PasswordEncoder passwordEncoder) {
        if (instance == null) {
            synchronized (PasswordAuthenticationProvider.class) {
                if (instance == null) {
                    instance = new PasswordAuthenticationProvider(authorizationService, tokenGenerator, userDetailsService, passwordEncoder);
                }
            }
        }
        return instance;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Statistics.logInfo(log, "authenticate", authentication);
        Map<String, Object> params = ((OAuth2AuthorizationGrantAuthenticationToken)authentication).getAdditionalParameters();
        UsernamePasswordAuthenticationToken unauthenticated = UsernamePasswordAuthenticationToken.unauthenticated(params.get("username"), params.get("password"));
        Authentication authenticationToken = daoAuthenticationProvider.authenticate(unauthenticated);
        return AuthenticationUtils.extractMethod(authorizationService, tokenGenerator, authentication, authenticationToken,
                authorizationGrantType);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        Statistics.logInfo(log, "supports", authentication);
        return PasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
